/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */

import { ISensor } from "../ISensor";
import { StackingSensor } from "../StackingSensor";
import { BoolReflectionSensor } from "./BoolReflectionSensor";
import { NumberReflectionSensor } from "./NumberReflectionSensor";
import { QuaternionReflectionSensor } from "./QuaternionReflectionSensor";
import { ReflectionSensorInfo } from "./ReflectionSensorBase";
import { Vector2ReflectionSensor } from "./Vector2ReflectionSensor";
import { Vector3ReflectionSensor } from "./Vector3ReflectionSensor";
import { Vector4ReflectionSensor } from "./Vector4ReflectionSensor";

export const typeToSensorInfo: { [key: string]: [number, any] } = {
    "Number": [1, NumberReflectionSensor],
    "Boolean": [1, BoolReflectionSensor],

    "Vec2": [2, Vector2ReflectionSensor],
    "Vec3": [3, Vector3ReflectionSensor],
    "Vec4": [4, Vector4ReflectionSensor],
    "Quat": [4, QuaternionReflectionSensor]
};

export class ObservableAttribute {
    public name: string;
    public numStackedObservations: number;
    constructor(name: string, numStackedObservations: number) {
        this.name = name;
        this.numStackedObservations = numStackedObservations;
    }

    static getObservableFields(o: object, excludeInherited: boolean): Map<string, ObservableAttribute> {
        const fields: Map<string, ObservableAttribute> = (o as any).constructor.observers;
        if (excludeInherited) {
            return fields;
        } else {
            const parent = Object.getPrototypeOf(o);
            if (parent.constructor.observers) {
                for (const [key, value] of parent.constructor.observers) {
                    fields.set(key, value);
                }
            }
            return fields;
        }
    }

    static createObservableSensors(o: object, excludeInherited: boolean): ISensor[] {
        const sensors: ISensor[] = [];
        const fields = ObservableAttribute.getObservableFields(o, excludeInherited);
        for (const [field, attr] of fields) {
            const sensor = ObservableAttribute.createReflectionSensor(o, field, attr);
            if (sensor) {
                sensors.push(sensor);
            }
        }
        return sensors;
    }

    static createReflectionSensor(o: object, filedName: string, observableAttribute: ObservableAttribute): ISensor | null {

        const type = (o as any)[filedName].constructor.name;
        if (!typeToSensorInfo[type]) {
            return null;
        }

        let sensorName = '';
        if (observableAttribute.name === '') {
            sensorName = `ObservableAttribute_${type}_${filedName}`;
        } else {
            sensorName = observableAttribute.name;
        }

        const reflectionSensorInfo: ReflectionSensorInfo = {
            obj: o,
            sensorName: sensorName,
            filedName: filedName

        };

        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const [_, sensorType] = typeToSensorInfo[type];
        const sensor = new sensorType(reflectionSensorInfo);

        if (observableAttribute.numStackedObservations > 1) {
            return new StackingSensor(sensor, observableAttribute.numStackedObservations);
        }
        return sensor;

    }

    /**
     * @en
     * Gets the sum of the observation sizes of the Observable fields and properties on an object.
     * Also appends errors to the errorsOut array.
     * 
     * @zh
     * 获取对象上的 Observable 字段和属性的观察大小之和。
     * 还将错误附加到 errorsOut 数组。
     * @param o 
     * @param excludeInherited 
     */
    static getTotalObservationSize(o: object, excludeInherited: boolean): [number, string[]] {
        let totalSize = 0;
        const errors: string[] = [];
        const fields = ObservableAttribute.getObservableFields(o, excludeInherited);
        for (const [field, attr] of fields) {
            const type = (o as any)[field].constructor.name;
            if (!typeToSensorInfo[type]) {
                errors.push(`ObservableAttribute ${field} has unsupported type ${type}`);
            } else {
                const [size, _] = typeToSensorInfo[type];
                totalSize += size;
            }
        }
        return [totalSize, errors];
    }


}

/**
* 属性观察者装饰器
* @param name Optional override for the sensor name. Note that all sensors for an Agent must have a unique name.
* @param numStackedObservations Number of frames to concatenate observations from.
*/
export function observable(name: string = '', numStackedObservations: number = 1) {

    return function (target: any, key: string) {
        if (!target.constructor.observers) {
            target.constructor.observers = new Map<string, ObservableAttribute>();
        }
        target.constructor.observers.set(key, new ObservableAttribute(name, numStackedObservations));
    }

}
